package service

import (
	"deng_wenyi/gen/cli"
	"deng_wenyi/gen/library"
	"fmt"
	"github.com/russross/blackfriday/v2"
	"github.com/yuin/goldmark"
	meta "github.com/yuin/goldmark-meta"
	"github.com/yuin/goldmark/text"
	"io/fs"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"text/template"
	"time"
)

type ListItem struct {
	Href string
	Text string
}

type Crumbs = ListItem

type Index struct {
	Description   string
	Title         string
	BigTitle      string
	SubTitle      string
	ListTitle     string
	ListItems     []*ListItem
	ReadmeTitle   string
	ReadmeContent string
	Crumbs        []*Crumbs
}

type Content struct {
	Description string
	Title       string
	Crumbs      []*Crumbs
	Content     string
	contentOther
}

type contentOther struct {
	OtherPage bool
	Time      time.Time
}

type serviceData struct {
	otherFiles   []string
	notUsedFiles []string
	useFiles     []string
	index        *Index
	content      []*Content
}

func Service(path *cli.Path) {
	var data *serviceData
	s, err := readData(path, data)
	if err != nil {
		return
	}
	err = dataProcessing(s)
	if err != nil {
		return
	}
	//TODO 生成html
	//TODO 未使用img文件的清理
	//otherFiles是当前可达的路径，因为是通过filepath.WalkDir遍历的；
	//而useFiles是Markdown中引用的路径，当前不可达。
}

func readData(path *cli.Path, data *serviceData) (*serviceData, error) {
	err := filepath.WalkDir(path.Markdown, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		if filepath.Ext(path) != ".md" {
			data.otherFiles = append(data.otherFiles, path)
			return nil
		}
		switch filepath.Base(path) {
		case "index.md":
			err = index(path, data)
			if err != nil {
				return err
			}
		default:
			otherPage, err := checkOtherPage(path)
			if err != nil {
				return err
			}
			fileInfo, err := d.Info()
			if err != nil {
				return err
			}
			switch otherPage {
			case false:
				content, err := content(path)
				if err != nil {
					return err
				}
				data.content = append(data.content, &Content{
					Description: "",
					Title:       d.Name()[0 : len(d.Name())-3],
					Crumbs:      nil,
					Content:     string(content),
					contentOther: contentOther{
						Time: fileInfo.ModTime(),
					},
				})
				useFiles, err := searchUseFiles(path)
				if err != nil {
					return err
				}
				data.useFiles = append(data.useFiles, useFiles...)
			case true:
				data.content = append(data.content, &Content{
					Description: "",
					Title:       d.Name()[0 : len(d.Name())-3],
					Crumbs:      nil,
					Content:     "",
					contentOther: contentOther{
						OtherPage: true,
						Time:      fileInfo.ModTime(),
					},
				})
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return data, nil
}

func index(path string, data *serviceData) error {
	file, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	markdown := goldmark.New(
		goldmark.WithExtensions(
			meta.New(
				meta.WithStoresInDocument(),
			),
		),
	)
	document := markdown.Parser().Parse(text.NewReader([]byte(file)))
	metaData := document.OwnerDocument().Meta()
	data.index.Description = metaData["Description"].(string)
	data.index.Title = metaData["Title"].(string)
	data.index.BigTitle = metaData["BigTitle"].(string)
	data.index.SubTitle = metaData["SubTitle"].(string)
	data.index.ListTitle = metaData["ListTitle"].(string)
	data.index.ReadmeTitle = metaData["ReadmeTitle"].(string)
	data.index.ReadmeContent = metaData["ReadmeContent"].(string)
	return nil
}

func checkOtherPage(path string) (bool, error) {
	file, err := os.ReadFile(path)
	if err != nil {
		return false, err
	}
	markdown := goldmark.New(
		goldmark.WithExtensions(
			meta.New(
				meta.WithStoresInDocument(),
			),
		),
	)
	document := markdown.Parser().Parse(text.NewReader([]byte(file)))
	metaData := document.OwnerDocument().Meta()
	if metaData["OtherPage"] != nil {
		return metaData["OtherPage"].(bool), nil
	}
	return false, nil
}

func content(path string) ([]byte, error) {
	file, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	return blackfriday.Run(file), nil
}

func searchUseFiles(path string) ([]string, error) {
	file, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var useFiles []string
	blackfriday.New().Parse(file).Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
		//查找所有引入markdown文件
		if node.Type != blackfriday.Image && node.Type != blackfriday.Link {
			return blackfriday.GoToNext
		}
		switch node.Type {
		case blackfriday.Image:
			if node.LinkData.Destination == nil || len(node.LinkData.Destination) == 0 {
				return blackfriday.GoToNext
			}
			useFiles = append(useFiles, string(node.LinkData.Destination))
		case blackfriday.Link:
			if node.FirstChild == nil || node.FirstChild.Type != blackfriday.Image {
				return blackfriday.GoToNext
			}
			if node.FirstChild.LinkData.Destination == nil || len(node.FirstChild.LinkData.Destination) == 0 {
				return blackfriday.GoToNext
			}
			useFiles = append(useFiles, string(node.FirstChild.LinkData.Destination))
		}
		return blackfriday.GoToNext
	})
	return useFiles, nil
}

func dataProcessing(data *serviceData) error {
	var newContent []*Content
	for _, v := range data.content {
		newContent = append(newContent, &Content{
			Description: fmt.Sprintf("%s%s", data.index.Description, v.Title),
			Title:       v.Title,
			Crumbs:      library.LikeWendy(v.Time, v.Title),
			Content:     v.Content,
			contentOther: contentOther{
				OtherPage: v.OtherPage,
				Time:      v.Time,
			},
		})
	}
	var listItems []*ListItem
	for _, v := range newContent {
		listItem, err := library.LikeWendy2(v.Crumbs)
		if err != nil {
			return err
		}
		listItems = append(listItems, &ListItem{
			Href: listItem.Href,
			Text: listItem.Text,
		})
	}
	newData := &serviceData{
		otherFiles:   data.otherFiles,
		notUsedFiles: data.notUsedFiles,
		useFiles:     data.useFiles,
		index: &Index{
			Description:   data.index.Description,
			Title:         data.index.Title,
			BigTitle:      data.index.BigTitle,
			SubTitle:      data.index.SubTitle,
			ListTitle:     data.index.ListTitle,
			ListItems:     listItems,
			ReadmeTitle:   data.index.ReadmeTitle,
			ReadmeContent: data.index.ReadmeContent,
			Crumbs: []*Crumbs{
				{Href: "/", Text: data.index.Title},
			},
		},
		content: newContent,
	}
	*data = *newData
	return nil
}

func genHTML(data *serviceData, path *cli.Path) error {
	temDir, err := os.MkdirTemp("", filepath.Join(time.Now().Format("2006-01-02 15:04:05"), "dist"))
	if err != nil {
		return err
	}
	defer os.RemoveAll(temDir)

	const index = "index.html"
	indexTemplate, err := template.New(index).ParseFiles(filepath.Join(path.Template, "index.gohtml"))
	if err != nil {
		return err
	}
	indexFile, err := os.Create(filepath.Join(temDir, index))
	if err != nil {
		return err
	}
	err = indexTemplate.Execute(indexFile, data.index)
	if err != nil {
		return err
	}
	indexFile.Close()

	for _, v := range data.content {
		switch v.OtherPage {
		case true:
			timeDir, err := url.PathUnescape(v.Crumbs[0].Href)
			if err != nil {
				return err
			}
			mkdirPath := filepath.Join(temDir, timeDir)
			err = os.Mkdir(mkdirPath, 750)
			if err != nil {
				return err
			}
			otherDirPath, err := url.PathUnescape(v.Crumbs[1].Href)
			if err != nil {
				return err
			}
			//判断路径是否存在
			if _, err := os.Stat(filepath.Join(path.OtherPage, otherDirPath)); os.IsNotExist(err) {
				return err
			}
			err = exec.Command("cp", "-r", filepath.Join(path.OtherPage, otherDirPath), mkdirPath).Run()
			if err != nil {
				return err
			}
		case false:
			contentTemplate, err := template.New("content").ParseFiles(filepath.Join(path.Template, "content.gohtml"))
			if err != nil {
				return err
			}
			timeDir, err := url.PathUnescape(v.Crumbs[0].Href)
			if err != nil {
				return err
			}
			nameDirPath, err := url.PathUnescape(v.Crumbs[1].Href)
			if err != nil {
				return err
			}
			mkdirPath := filepath.Join(temDir, timeDir, nameDirPath)
			err = os.MkdirAll(mkdirPath, 750)
			if err != nil {
				return err
			}
			contentFile, err := os.Create(filepath.Join(mkdirPath, "index.html"))
			if err != nil {
				return err
			}
			err = contentTemplate.Execute(contentFile, v)
			if err != nil {
				return err
			}
			contentFile.Close()
		}
	}

	//拷贝静态文件
	err = exec.Command("cp", "-r", path.Static, temDir).Run()
	if err != nil {
		return err
	}

	//拷贝其他文件
	for _, v := range data.useFiles {
		//获取v中的路径
		//filepath.Join(temDir, filepath.Dir(v))

		//TODO 先睡觉了，看上去需要在Markdown变成html之前将路径替换掉，并且需要防止文件名重复，我打算使用map。

		err = exec.Command("cp", v, temDir).Run()
		if err != nil {
			return err
		}
	}
}
